package uml;

import static uml.XMIVisitor.ASSOCIATIONS;
import static uml.XMIVisitor.GENERALIZATIONS;
import static uml.XMIVisitor.PACKAGES;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import uml.internal.Association;
import uml.internal.Association.AssociationEnd;
import uml.internal.Generalisation;
import util.FileUtils;

import components.Argument;
import components.Class;
import components.Field;
import components.Method;

public class XMIOutputter {
	//- PUBLIC & INTERNAL VARIABLES ---------------------------------------------------------------------------

	//- PRIVATE & PROTECTED VARIABLES -----------------------------------------------------------------------
	private static final String TEMPLATE = "template.xml";
	private String template;
	
	// bufory na dane
	private final Map<String, StringBuffer> datas = new HashMap<String, StringBuffer>();
	{
		datas.put(PACKAGES, new StringBuffer());
		datas.put(ASSOCIATIONS, new StringBuffer());
		datas.put(GENERALIZATIONS, new StringBuffer());
	}
	
	//- CONSTRUCTOR ----------------------------------------------------------------------------------------------
	public XMIOutputter() throws URISyntaxException, IOException {
		// inicjalizacja template'a
		URI uri = XMIOutputter.class.getResource(TEMPLATE).toURI();
		File file = new File(uri);
		template = FileUtils.readFileAsString(file);
	}
	
	//- PUBLIC & INTERNAL METHODS -----------------------------------------------------------------------------
	public void openPackage(String name) {
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("<UML:Package xmi.id=\"" + name + "\" " +
				"name=\"" + name + "\" " +
				"namespace=\"UMLModel.3\">");
		
		sb.append("<UML:Namespace.ownedElement>");
	}

	public void closePackage() {
		StringBuffer sb = datas.get(PACKAGES);
		
        sb.append("\n</UML:Namespace.ownedElement>");
        sb.append("\n</UML:Package>");
	}

	/**
	 * Wprowadza zmiany do szablonu
	 */
	public void commit() {
		for (Map.Entry<String, StringBuffer> entry : datas.entrySet()) {
			template = template.replace(entry.getKey(), entry.getValue());
		}
	}
	
	@Override
	public String toString() {
		return template;
	}

	public void store(File file) throws IOException {
		FileWriter fw = null;
		try {
			fw = new FileWriter(file);
			fw.write(template);
		} finally {
			if(fw != null) fw.close();
		}
	}

	public void openClass(Class c, int classId, String packageName) {
		StringBuffer sb = datas.get(PACKAGES);
		
		String className = c.getName();
		String abs = c.getAbstract() != null && c.getAbstract().equals("abstract") ? "true" : "false" ;
		String visiblity = c.getAccessQualifier();
		
		String classType = (c.isInterface() == false) ? "Class" : "Interface" ;
		sb.append("\n<UML:" + classType + " ");
		//sb.append("xmi.id=\"UMLClass."+classId+"\" ");
		sb.append("xmi.id=\""+className+"\" ");
		sb.append("name=\"" + className + "\" ");
		sb.append("visibility=\"" + visiblity + "\" ");
		sb.append("namespace=\""+ packageName + "\" ");
		sb.append("isAbstract=\"" + abs + "\" >");
		sb.append("\n<UML:Classifier.feature>");
	}

	public void closeClass(Class c) {
		StringBuffer sb = datas.get(PACKAGES);
		
		//sb.append("\n</UML:Classifier.feature>");
		String classType = (c.isInterface() == false) ? "Class" : "Interface" ;
		sb.append("\n</UML:" + classType + ">\n\n");
	}

	public void openCloseField(Field f, int fieldId, int classId) {
		/* <UML:Attribute xmi.id="UMLAttribute.6" 
		 * name="test" 
		 * visibility="protected" 
		 * changeability="changeable" 
		 * type="X.20" owner="UMLClass.5"/>
		 */
		
		String changeability = f.getFinal() != null && f.getFinal().equals("final") ? "false" : "changeable" ;
		
		StringBuffer sb = datas.get(PACKAGES);
		sb.append("\n<UML:Attribute ");
		sb.append("xmi.id=\"UMLAttribute." + fieldId + "\" ");
		sb.append("name=\""+f.getName()+"\" ");
		sb.append("visibility=\"" + f.getAccessQualifier() + "\" ");
		sb.append("changeability=\"" + changeability + "\" ");
		sb.append("type=\"" + ((f.getType() != null) ? formatType(f.getType()) : "") + "\" ");
		sb.append("owner=\"UMLClass." + classId + "\" />");
	}

	public void openMethod(Method m, int operationId, int classId) {
		/* <UML:Operation 
			xmi.id="UMLOperation.7" 
			name="test" 
			visibility="public" 
			concurrency="sequential" 
			isAbstract="false" 
			owner="UMLClass.5">
          
          */
		
		String abs = m.getAbstract() != null && m.getAbstract().equals("abstract") ? "true" : "false" ;	
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("\n<UML:Operation ");
		sb.append("xmi.id=\"UMLOperation."+ operationId +"\" ");
		sb.append("name=\"" + m.getName() + "\" ");
		sb.append("visibility=\"" + m.getAccessQualifier() + "\" ");
		sb.append("concurrency=\"" + m.getSynchronized() + "\" ");
		sb.append("isAbstract=\"" + abs + "\" ");
		sb.append("owner=\"UMLClass." + classId + "\">");
		
		sb.append("\n<UML:BehavioralFeature.parameter>");
	}

	public void closeMethod() {
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("\n</UML:BehavioralFeature.parameter>");
		sb.append("\n</UML:Operation>");
	}

	public void openCloseArgument(Argument a, int parameterId, int operationId, int classId, String kind) {
		/*<UML:Parameter 
		 * xmi.id="UMLParameter.8" 
		 * name=""
		 * kind="return" 
		 * behavioralFeature="UMLOperation.7" 
		 * type=""/>
		 */
		
		StringBuffer sb = datas.get(PACKAGES);
		sb.append("\n<UML:Parameter ");
		sb.append("xmi.id=\"UMLParameter."+parameterId+"\" ");
		sb.append("name=\"" + a.getName() +"\" ");
		sb.append("kind=\"" + kind + "\" ");
		sb.append("behavioralFeature=\"UMLOperation."+operationId+"\" ");
		sb.append("type=\"" + ((a.getType() != null) ? formatType(a.getType()) : "") + "\" /> ");
		
	}
	
	public void closeClassFeatures() {
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("\n</UML:Classifier.feature>");
	}

	public void openInnerClass() {
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("\n<UML:Namespace.ownedElement>");
	}

	public void closeInnerClass() {
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("</UML:Namespace.ownedElement>");
	}

	public void openCloseStereoptype(String id, String name, String extendedElement) {
		//<UML:Stereotype xmi.id="X.25" name="enumeration" extendedElement="UMLEnumeration.16"/
		StringBuffer sb = datas.get(PACKAGES);
		
		sb.append("<UML:Stereotype xmi.id=\""+ id +"\" name=\""+name+"\" extendedElement=\""+ extendedElement + "\"/>");
	}

	public void openCloseGeneralisation(Generalisation f) {
		/* <UML:Generalization xmi.id="UMLGeneralization.21" name="" visibility="public" 
		* isSpecification="false"
		* namespace="UMLModel.2" 
		* discriminator="" 
		* child="UMLClass.9" 
		* parent="UMLClass.4"/>
		*/
	
		String type = f.getRelationType();
		String childType = (type.equals("Abstraction")) ? "supplier" : "parent";
		String supType = (type.equals("Abstraction")) ? "client" : "child";
		
		StringBuffer sb = datas.get(GENERALIZATIONS);
		
		sb.append("\n<UML:"+type+" ");
		sb.append("xmi.id=\""+type+"."+f.getId()+"\" ");
		sb.append(supType + "=\"" + formatType(f.getSubClass()) + "\" ");
		
		
		
		sb.append(childType+"=\"" + formatType(f.getSuperClass()) + "\" ");
		sb.append("namespace=\""+f.getPackageName()+"\" ");
		sb.append("/>");
	}

	public void openCloseAssociation(Association a) {
		/* <UML:Association 
		 * xmi.id="UMLAssociation.6" 
		 * name="pole" 
		 * visibility="public" 
		 * namespace="UMLModel.2">
		 * 
		 * 	<UML:Association.connection>
		 *  ...
		 *  </UML:Association.connection>
		 *  
		 *  </UML:Association>
		 */
		
		StringBuffer sb = datas.get(ASSOCIATIONS);
		
		sb.append("\n<UML:Association ");
		sb.append("xmi.id=\"UMLAssociation."+a.getId()+"\" ");
		sb.append("name=\""+a.getName()+"\" ");
		sb.append("namespace=\""+a.getPackageName()+"\"> ");
		
		sb.append("<UML:Association.connection> ");
		
		sb.append(openCloseAssociationEnd(a.getBegin()));
		sb.append(openCloseAssociationEnd(a.getEnd()));
		
		sb.append("</UML:Association.connection>");
		sb.append("</UML:Association>");
	}

	
	//- PRIVATE & PROTECTED METHODS -------------------------------------------------------------------------
	private String formatType(String type){
		String left = type.replaceAll("<", "&lt;");
		return left.replaceAll(">", "&gt;");
	}

	private String openCloseAssociationEnd(AssociationEnd begin) {
		/*
		 *  <UML:AssociationEnd 
		 *  xmi.id="UMLAssociationEnd.8" 
		 *  name=""
		 *  isNavigable="true" 
		 *  association="UMLAssociation.6" 
		 *  type="UMLClass.5"/>
		 */
		
		StringBuffer sb = new StringBuffer();
		sb.append("<UML:AssociationEnd "); 
		sb.append("xmi.id=\"UMLAssociationEnd."+begin.getId()+"\" "); 
		sb.append("isNavigable=\""+begin.isNavigable()+"\" "); 
		sb.append("association=\"UMLAssociation."+begin.getAssociationId()+"\" "); 
		sb.append("type=\""+ ((begin.getType() != null) ? formatType(begin.getType()) : "") +"\"/>"); 
		
		return sb.toString();
	}

	//- GETTERS & SETTERS ----------------------------------------------------------------------------------------

}
